package webx

import (
	"encoding/json"
	"log/slog"
	"net/url"
	"sort"
	"strings"
	"sync"

	"gitee.com/xungen/goweb/errno"
	"gitee.com/xungen/goweb/utils"
)

type accessItem struct {
	param  map[string]string
	group  []string
	remote bool
	length int
}

var accessversion string
var accessrwmutex sync.RWMutex
var accesspathmap = make(map[string][]accessItem)

func updateAccess(host string, port int) bool {
	defer func() {
		if err := recover(); err == nil {
			slog.Info("update access list success")
		} else {
			slog.Error("update access list error", errno.Attr(err))
		}
	}()

	type Item struct {
		Path      string `json:"path"`
		Param     string `json:"param"`
		GroupList string `json:"grouplist"`
	}

	type Result struct {
		List []Item `json:"list"`
	}

	buff, version := graspAccess(host, port)
	pathmap := make(map[string][]accessItem)
	if version == "" || version == accessversion {
		return update(accessversion, pathmap)
	}

	data := &Result{}
	json.Unmarshal(buff, data)

	for _, item := range data.List {
		if len(item.GroupList) == 0 {
			continue
		}
		item.Path = FormatPath(item.Path)
		elem := accessItem{length: len(item.Param), remote: true, group: strings.Split(item.GroupList, ",")}
		if elem.length > 0 {
			args, err := url.ParseQuery(item.Param)
			if err == nil {
				elem.param = make(map[string]string)
				for k, v := range args {
					if len(v) > 0 {
						elem.param[k] = v[0]
					}
				}
			}
		}
		vec := append(pathmap[item.Path], elem)
		sort.Slice(vec, func(i, j int) bool {
			return vec[i].length > vec[j].length
		})
		pathmap[item.Path] = vec
	}

	return update(version, pathmap)
}

func CheckAccess(path string, group []string, request any) error {
	path = FormatPath(path)
	check := func(srclist, destlist []string) error {
		if len(destlist) == 0 {
			return errno.FAIL
		}
		for _, src := range srclist {
			if src == "root" {
				return errno.OK
			}
			for _, dest := range destlist {
				if src == dest || dest == "public" {
					return errno.OK
				}
			}
		}
		return errno.AUTHFAIL
	}

	var end accessItem
	vec := getPathAccessList(path)
	paramap := utils.GetSimpleFields(request)

	for _, v := range vec {
		end = v
		if v.param == nil {
			return check(group, v.group)
		}
		num := 0
		for k, v := range v.param {
			if v == paramap[k] {
				num++
			} else {
				num = 0
				break
			}
		}
		if num > 0 {
			return check(group, v.group)
		}
	}

	return check(group, end.group)
}

func getPathAccessList(path string) []accessItem {
	path = FormatPath(path)
	accessrwmutex.RLock()
	vec := accesspathmap[path]
	accessrwmutex.RUnlock()
	return vec
}

func update(version string, pathmap map[string][]accessItem) bool {
	for path, item := range docmap {
		if item.group == "" {
			continue
		}
		if _, ok := pathmap[path]; ok {
			continue
		}
		elem := accessItem{group: strings.Split(item.group, ",")}
		pathmap[item.path] = []accessItem{elem}
	}

	accessrwmutex.Lock()
	accessversion, accesspathmap = version, pathmap
	accessrwmutex.Unlock()
	return true
}

func graspAccess(host string, port int) (result []byte, version string) {
	defer func() {
		if err := recover(); err != nil {
			slog.Error("grasp access error", errno.Attr(err))
			version = accessversion
		}
	}()
	link := GetLink(host, port, "exportaccess")
	header := map[string]string{"If-None-Match": accessversion}
	response := errno.AssertNoError(getHttpResult(link, nil, header))
	result = errno.AssertNoError(readFromHttpResponse(response))
	version = GetHttpEtag(response)
	return result, version
}
